﻿#region 참조 네임스페이스

using Skying.ComponentModel;
using System;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;

#endregion

namespace Skying.Net.Sockets
{
	/// <summary>
	/// <see cref="BaseSocket"/>을 기반으로 하는 TCP 소켓 클라이언트를 구현한다.
	/// </summary>
	/// <remarks>
	/// <para>데이터 송수신에 대한 인터페이스는 <see cref="BaseSocket"/>으로 부터 상속받는다.</para>
	/// <para><see cref="TcpListener"/>로의 연결을 위해 <see cref="TcpClient.ConnectAsynchrous"/> 메서드를 제공한다.</para>
	/// </remarks>
	public sealed class TcpClient : BaseSocket
	{
		#region 프로퍼티

		/// <summary>
		/// 연결 수용 조건의 사용 여부를 가져온다.
		/// </summary>
		public bool UseAcceptCondition
		{
			get;
			private set;
		}

		/// <summary>
		/// 클라이언트가 리스너로 연결됐을 때 실행한다.
		/// </summary>
		public Action<AsyncCompletedEventArgs> Connected
		{
			get;
			set;
		}

		#endregion

		#region 생성자/소멸자

		public TcpClient(bool useInternalHeader = true, bool useAcceptCondition = true)
			: this(_DEFAULT_BUFFER_SIZE, useInternalHeader, useAcceptCondition)
		{

		}

		public TcpClient(int bufferSize, bool useInternalHeader = true, bool useAcceptCondition = true)
			: base(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp, useInternalHeader)
		{
			SynchronizationContextRegister.Verify(SynchronizationContextProperties.NonReentrantPost | SynchronizationContextProperties.NonReentrantSend | SynchronizationContextProperties.Sequential | SynchronizationContextProperties.Synchronized);

			base._pieceBufferSize = bufferSize;

			this.UseAcceptCondition = useAcceptCondition;
		}

		#endregion

		#region 소켓 컨트롤

		/// <summary>
		/// 원격 호스트에 비동기적으로 연결한다.
		/// </summary>
		/// <param name="remoteEndPoint">원격 끝점 정보를 설정한다.</param>
		/// <param name="userToken">클라이언트 연결 후에 <see cref=""/> 사용자 정의 정보를 설정한다.</param>
		public void ConnectAsynchrous(IPEndPoint remoteEndPoint, string userToken = null)
		{
			try
			{
				base.Address = remoteEndPoint.Address;
				base.Port = remoteEndPoint.Port;

				base._socket.BeginConnect(remoteEndPoint, Synchronization.SynchronizeAsyncCallback((asyncResult) =>
				{
					Synchronization.InvokeAndCallback(() =>
					{
						base._socket.EndConnect(asyncResult);

						base.TurnKeepAlive(true, 60 * 1000, 1000);

						// 연결 수용 조건을 사용해야 하는 경우
						if (this.UseAcceptCondition)
						{
							// 연결 수용 조건을 리스너로 전송한다.
							this.SendAcceptCondition(new AcceptCondition(base._id, this.Endian, userToken));
						}

						// 데이터 수신을 시작한다.
						base.ReceiveAsynchrous();
					},
					Connected, null);
				}), null);
			}
			catch (Exception error)
			{
				base.ThrowError(error);
			}
		}

		/// <summary>
		/// 리스너로 Accept 조건을 전송한다.
		/// </summary>
		/// <param name="acceptCondition">Accept 조건을 전송한다.</param>
		internal void SendAcceptCondition(AcceptCondition acceptCondition)
		{
			try
			{
				this._socket.Send(acceptCondition.ToByteArray(this.Endian == Endians.Little));
			}
			catch (Exception error)
			{
				base.ThrowError(error);
			}
		}

		#endregion
	}
}